﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.Data;
using StyleMVVM.DependencyInjection;
using StyleMVVM.Logging;
using StyleMVVM.Utilities;
using StyleMVVM.Validation.Impl.Fluent;

namespace StyleMVVM.Validation.Impl.Method
{
	[Export(typeof(IValidationRuleProvider))]
	public class ValidationMethodRuleProvider : IValidationRuleProvider
	{
		private static readonly MethodInfo createMethod;
		private readonly IGlobalCache cache;
		private readonly IDependencyInjectionContainer container;
		private readonly IReflectionService reflectionService;
		private static readonly string supplemental = typeof(ValidationMethodRuleProvider).FullName;

		static ValidationMethodRuleProvider()
		{
			createMethod = typeof(ValidationMethodRuleProvider).GetTypeInfo().GetDeclaredMethod("CreateRules");
		}

		[ImportConstructor]
		public ValidationMethodRuleProvider(IDependencyInjectionContainer container,
		                                    IGlobalCache cache,
		                                    IReflectionService reflectionService)
		{
			this.cache = cache;
			this.container = container;
			this.reflectionService = reflectionService;
		}

		public IEnumerable<IValidationRule> ProvideRules(Type objectType, IEnumerable<string> standards)
		{
			List<IValidationRule> returnValue = new List<IValidationRule>();

			Tuple<IEnumerable<IValidationRule>, IEnumerable<string>> ruleInfos = FetchValidationInfo(objectType);

			if (standards != null)
			{
				List<string> localStandards = new List<string>(standards);

				foreach (IValidationRule validationRule in ruleInfos.Item1)
				{
					IEnumerable<string> ruleStandards = validationRule.Standards();

					if (ruleStandards != null)
					{
						foreach (string standard in ruleStandards)
						{
							if (localStandards.Contains(standard))
							{
								returnValue.Add(validationRule);
								break;
							}
						}
					}
				}
			}
			else
			{
				returnValue.AddRange(ruleInfos.Item1);
			}

			return returnValue;
		}

		public IEnumerable<string> GetPropertiesToValidate(Type objectType, IEnumerable<string> standards)
		{
			Tuple<IEnumerable<IValidationRule>, IEnumerable<string>> ruleInfos = FetchValidationInfo(objectType);

			return ruleInfos.Item2;
		}

		private Tuple<IEnumerable<IValidationRule>, IEnumerable<string>> FetchValidationInfo(Type objectType)
		{
			string cacheKey = "ValidationMethod|Rule|" + objectType.FullName;

			Tuple<IEnumerable<IValidationRule>, IEnumerable<string>> returnValue =
				cache.GetValue(cacheKey) as Tuple<IEnumerable<IValidationRule>, IEnumerable<string>>;

			if (returnValue == null)
			{
				MethodInfo genericMethod = createMethod.MakeGenericMethod(objectType);

				returnValue =
					genericMethod.Invoke(this, new object[] { }) as Tuple<IEnumerable<IValidationRule>, IEnumerable<string>>;

				cache.AddWithSlidingExpireTime(cacheKey, returnValue, new TimeSpan(0, 15, 0));
			}

			return returnValue;
		}

		private Tuple<IEnumerable<IValidationRule>, IEnumerable<string>> CreateRules<T>()
		{
			List<string> validationProperties = new List<string>();
			List<IValidationRule> validationRules = new List<IValidationRule>();

			IEnumerable<IValidationMethodProvider<T>> methodRuleProviders =
				container.LocateAll<IValidationMethodProvider<T>>();

			ValidationMethodCollection<T> validationMethodCollection = new ValidationMethodCollection<T>();

			foreach (IValidationMethodProvider<T> methodRuleProvider in methodRuleProviders)
			{
				try
				{
					methodRuleProvider.ProvideRules(validationMethodCollection);
				}
				catch (Exception exp)
				{
					Logger.Error("Exception was thrown while processing fluent validation rule for type: " + typeof(T).FullName,
					             supplemental,
					             exp);
				}
			}

			validationRules.AddRange(validationMethodCollection.AllRules());
			validationProperties.AddRange(validationMethodCollection.AllProperties());

			return new Tuple<IEnumerable<IValidationRule>, IEnumerable<string>>(validationRules, validationProperties);
		}
	}
}